package com.glodon.paas.account.security.common;

import com.glodon.paas.account.biz.UserBiz;
import com.glodon.paas.account.security.oauth1.filter.RememberRequestUrlFilter;
import com.glodon.paas.account.security.openid.service.OpenIdService;
import com.glodon.paas.account.security.spring.LoginUser;
import com.glodon.paas.util.SimpleBeanConverter;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.ticket.Ticket;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.ticket.registry.TicketRegistry;
import org.jasig.cas.web.support.CookieRetrievingCookieGenerator;
import org.openid4java.message.*;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.glodon.paas.account.security.cas.filter.AutoLoginFilter.AUTO_LOGIN_USER;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;

/**
 * Process OpenID and CAS related jobs
 *
 * @author Don Li
 */
@Component
public class PostAuthHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PostAuthHandler.class);

    @Value("${openid.id.base.url}")
    private String identityBaseUrl;

    @Value("${openid.claimedid.base.url}")
    private String claimedIdBaseUrl;

    @Autowired
    private ServerManager openidServerManager;

    @Autowired
    private CentralAuthenticationService cas;

    @Autowired
    @Qualifier("grantingTicketRegistry")
    private TicketRegistry ticketRegistry;

    @Autowired
    private CookieRetrievingCookieGenerator tgcGenerator;

    @Autowired
    private ServicesManager casServicesManager;

    @Autowired
    private UserBiz userBiz;

    @Autowired
    @Qualifier("userAuthenticationManager")
    private AuthenticationManager authenticationManager;


    public void process(HttpServletRequest request, HttpServletResponse response, LoginUser user, String serviceId, String tgtId, FilterChain chain) throws IOException, ServletException {
        ParameterList requestParams = (ParameterList) request.getSession().getAttribute(OpenIdService.OPENID_REQUEST_PARAM);
        if (requestParams != null) { // replaying party login
            request.getSession().removeAttribute(OpenIdService.OPENID_REQUEST_PARAM);
            Message message = createOpenidAuthResponse(user, requestParams);
            if (message instanceof DirectError) {
                LOGGER.error("Fail to create openid success auth response message.");
                throw new ServletException(((DirectError) message).getException());
            }
            String url = message.getDestinationUrl(true);
            String st = createServiceTicket(serviceId, tgtId);
            sendUrlRedirect(response, url + "&st=" + st);
        } else if ("account".equals(serviceId)) { // account login
            request.getSession(false).setAttribute(AUTO_LOGIN_USER, user);
            String url = (String) request.getSession(false).getAttribute(RememberRequestUrlFilter.REMEMBERED_REQUEST_URL);
            if (isNullOrEmpty(url)) {
                sendUrlRedirect(response, request.getContextPath());
            } else {
                request.getSession(false).removeAttribute(RememberRequestUrlFilter.REMEMBERED_REQUEST_URL);
                sendUrlRedirect(response, url);
            }
        } else {
            throw new ServletException("Un-supported login type");
        }
    }

    public LoginUser getUserByTgtId(String tgtId) {
        Ticket ticket = ticketRegistry.getTicket(tgtId);
        if (ticket instanceof TicketGrantingTicket && !ticket.isExpired()) {
            String userid = ((TicketGrantingTicket) ticket).getAuthentication().getPrincipal().getId();
            return SimpleBeanConverter.converte(userBiz.getUser(userid), LoginUser.class);
        }
        return null;
    }

    public void removeTgt(String tgtId) {
        cas.destroyTicketGrantingTicket(tgtId);
    }

    private Message createOpenidAuthResponse(LoginUser user, ParameterList requestParams) {
        final Message message = openidServerManager.authResponse(requestParams, identityBaseUrl + user.getId(), claimedIdBaseUrl + user.getId(), true, false);
        fetchOpenidAttributes(user, requestParams, message);
        try {
            openidServerManager.sign((AuthSuccess) message);
        } catch (Exception e) {
            LOGGER.error("can't sign response message", e);
        }
        return message;
    }

    private void fetchOpenidAttributes(LoginUser user, ParameterList requestParams, Message responseMessage) {
        if ("fetch_request".equals(requestParams.getParameterValue("openid.ext1.mode"))) {
            String atrribute1 = requestParams.getParameterValue("openid.ext1.type.attr1");
            String atrribute2 = requestParams.getParameterValue("openid.ext1.type.attr2");
            FetchResponse fetchResponse = FetchResponse.createFetchResponse();
            if ("email".equals(atrribute1)) {
                fetchResponse.addAttribute("email", user.getEmail());
            }
            if ("username".equals(atrribute2)) {
                String userName = user.getUsername();
                fetchResponse.addAttribute("username", (userName == null) ? "" : userName);
            }
            try {
                responseMessage.addExtension(fetchResponse);
            } catch (MessageException e) {
                LOGGER.error("can't add attribute fetch to response", e);
            }
        }
    }

    private String createServiceTicket(String serviceId, String tgtId) {
        final Service service = new SimpleWebApplicationServiceImpl(serviceId);
        checkService(service);
        return grantServiceTicket(service, tgtId);
    }

    private void checkService(Service service) {
        checkNotNull(casServicesManager.findServiceBy(service));
    }

    private String grantServiceTicket(Service service, String tgtId) {
        String stId = null;
        try {
            stId = cas.grantServiceTicket(tgtId, service);
        } catch (TicketException e) {
            LOGGER.error("ST cannot be granted to service : {}", service.getId());
        }
        return stId;
    }

    private void sendUrlRedirect(HttpServletResponse response, String url) throws IOException {
        response.sendRedirect(url);
    }
}
